package me.zhengjie.modules.biz.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.exception.BizException;
import me.zhengjie.modules.biz.domain.Profession;
import me.zhengjie.modules.biz.domain.ScoreSort;
import me.zhengjie.modules.biz.domain.Universities;
import me.zhengjie.modules.biz.domain.Vip;
import me.zhengjie.modules.biz.domain.dto.TzRecommendDTO;
import me.zhengjie.modules.biz.domain.dto.TzRecommendUniversitiesDTO;
import me.zhengjie.modules.biz.domain.dto.UniversitiesMinScoreDTO;
import me.zhengjie.modules.biz.domain.req.TzRecommendReq;
import me.zhengjie.modules.biz.domain.resp.BaseRecommendResp;
import me.zhengjie.modules.biz.domain.resp.TzRecommendResp;
import me.zhengjie.modules.biz.domain.resp.UniversitiesRecommendResp;
import me.zhengjie.modules.biz.domain.req.UniversitiesRecommendReq;
import me.zhengjie.modules.biz.domain.vo.TzRecSubTypeQry;
import me.zhengjie.modules.biz.domain.vo.TzRecYearsQry;
import me.zhengjie.modules.biz.mapper.ProfessionMapper;
import me.zhengjie.modules.biz.mapper.ScoreSortMapper;
import me.zhengjie.modules.biz.mapper.UniversitiesMapper;
import me.zhengjie.modules.biz.mapper.VipMapper;
import me.zhengjie.modules.biz.service.CollegeEntranceExamService;
import me.zhengjie.modules.system.domain.WxUser;
import me.zhengjie.modules.system.mapper.WxUserMapper;
import me.zhengjie.utils.RedisUtils;
import me.zhengjie.utils.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static me.zhengjie.modules.biz.constant.GlobalConstant.*;
import static me.zhengjie.modules.biz.constant.GlobalConstant.ZK;
import static me.zhengjie.modules.biz.domain.enums.ErrorCode.*;
import static me.zhengjie.modules.biz.domain.enums.ErrorCode.RECOMMEND_NMU_MAX;
import static me.zhengjie.modules.pay.enums.StatusEnum.*;


/**
 * 高考志愿：实现类
 * @author Administrator
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class CollegeEntranceExamServiceImpl implements CollegeEntranceExamService {
    private final WxUserMapper wxUserMapper;
    private final VipMapper vipMapper;
    private final ProfessionMapper professionMapper;
    private final UniversitiesMapper universitiesMapper;
    private final ScoreSortMapper scoreSortMapper;
    private final RedisUtils redisUtils;


    @Override
    @Transactional
    public UniversitiesRecommendResp recommendUniversities(UniversitiesRecommendReq req) throws BizException {
        UniversitiesRecommendResp resp = new UniversitiesRecommendResp();
        TzRecommendDTO dto = new TzRecommendDTO();
        TzRecSubTypeQry tzRecSubTypeQry = new TzRecSubTypeQry();
        Double grossScore = req.getGrossScore();
        Integer tzExamType = req.getTzExamType();

        // 1.参数校验
        checkReq(req);
        checkIsFirst(req);

        // 2.查询redis中是否已有缓存数据
        String redisKey = TZ_RECOMMEND + tzExamType + "/" + grossScore;
        UniversitiesRecommendResp o = (UniversitiesRecommendResp) redisUtils.get(redisKey);
        if(o != null) {
            log.info("redis缓存：{}}",o);
            return o;
        }

        // 3. 一份一段，查看学生分数所在的位次
        ScoreSort scoreSort = scoreSortMapper.selectOne(new QueryWrapper<ScoreSort>().eq("score", grossScore).eq("category", req.getTzExamType()));
        if(scoreSort == null){
            throw new BizException(NOT_MATCH_SCORE_SORT.getCode(),NOT_MATCH_SCORE_SORT.getMessage());
        }

        // 4.筛选院校---院校最低录取专业分数的min_score【省控线】
        List<UniversitiesMinScoreDTO> dtoList = getUniversitiesMinScoreDTOS(tzExamType);

        List<String> unameList;
        // 4.1 按分数筛段选出院校名称
        dtoList = dtoList.stream().filter(r -> Objects.equals(r.getSubjectType(), tzExamType)).filter(r -> grossScore <= r.getMinScore() + 10).filter(r -> grossScore >= r.getMinScore() - 5).collect(Collectors.toList());
        unameList = dtoList.stream().map(UniversitiesMinScoreDTO::getUname).collect(Collectors.toList());
        if(unameList.isEmpty()){
            setRespObject(req, resp, scoreSort);
            return resp;
        }

        // 5.获取院校类型，分类
        List<Universities> universitiesList = universitiesMapper.selectList(new QueryWrapper<Universities>().select("name","university_quality","university_type","labels").in("name", unameList));

        // 6.获取院校全量数据以及各类型院校标签
        List<String> unameByTypeList = universitiesList.stream().map(Universities::getName).collect(Collectors.toList());
        tzRecSubTypeQry.setTzExamType(tzExamType);
        tzRecSubTypeQry.setUnameByTypeList(unameByTypeList);
        List<TzRecommendUniversitiesDTO> allUniversities = universitiesMapper.universitiesList(tzRecSubTypeQry);
        resp.setTotal(universitiesList.size());
        int governmentUniversitiesTotal = (int) universitiesList.stream().filter(universities -> universities.getUniversityQuality() == 0).count();//公办院校
        int independentUniversitiesTotal = (int) universitiesList.stream().filter(universities -> universities.getUniversityQuality() == 1).count();//民办院校
        int total985 = (int) universitiesList.stream().filter(universities -> universities.getLabels().contains("985")).count(); // 985
        int total211 = (int) universitiesList.stream().filter(universities -> universities.getLabels().contains("211")).count(); // 211
        int doubleTotal = (int) universitiesList.stream().filter(universities -> universities.getLabels().contains("双一流")).count(); // 双一流
        int provinceTotal  = (int) universitiesList.stream().filter(universities -> universities.getLabels().contains("省重点")).count(); // 省重点
        int hkAndMacaoTotal = (int) universitiesList.stream().filter(universities -> universities.getLabels().contains("港澳")).count(); // 港澳
        // 根据有效的院校数据，筛选稳 保 冲的院校
        List<String> realUnameList = universitiesList.stream().map(Universities::getName).collect(Collectors.toList());
        dtoList = dtoList.stream().filter(u -> realUnameList.contains(u.getUname())).collect(Collectors.toList());
        getUnameList(dtoList, tzExamType, grossScore, resp,0);

        // 封装响应参数
        setResultObject(req, resp, total211, total985, governmentUniversitiesTotal, independentUniversitiesTotal, doubleTotal, provinceTotal, hkAndMacaoTotal,scoreSort);

        // 7.将后续列表需要使用的数据存到redis, dtoList是【x-5,x+10】的院校省控线
        saveRecommendList(req, dto, resp, universitiesList,allUniversities,dtoList);

        return resp;
    }



    /**
     * 列表正常情况能从redis中取值，但防止后管新增院校时清空缓存，又使用了用户的荐校次数，isFirst = 0 时，单独查询数据库
     */
    @Override
    public TzRecommendResp universitiesList(TzRecommendReq req) throws BizException {
        List<String> unameByTypeList = null;
        List<TzRecommendUniversitiesDTO> list = new ArrayList<>();
        List<Universities> universitiesList;
        Double grossScore = req.getGrossScore();
        Integer tzExamType = req.getTzExamType();
        // 0:全部（一般不使用） 1：公办 2：民办 3：保 4：稳 5：冲 6：985 7：211 8：双一流 9：省重点 10：港澳
        Integer type = req.getType();
        WxUser wxUser = wxUserMapper.selectOne(new QueryWrapper<WxUser>().eq("id", req.getUserId()));

        // 1.初始化返回值
        TzRecommendResp resp = initResponse(req);

        // 2.获取redis中的数据
        TzRecommendDTO o = (TzRecommendDTO) redisUtils.get(TZ_RECOMMEND_LIST + req.getTzExamType() + "/" + req.getGrossScore());
        if(o == null){
            List<UniversitiesMinScoreDTO> dtoList = getUniversitiesMinScoreDTOS(tzExamType);
            // 按分数筛段选出院校名称
            dtoList = dtoList.stream().filter(r -> Objects.equals(r.getSubjectType(), tzExamType)).filter(r -> grossScore <= r.getMinScore() + 10).filter(r -> grossScore >= r.getMinScore() - 5).collect(Collectors.toList());
            List<String> unameList = dtoList.stream().map(UniversitiesMinScoreDTO::getUname).collect(Collectors.toList());
            if(unameList.isEmpty()) {
                return resp;
            }
            universitiesList = universitiesMapper.selectList(new QueryWrapper<Universities>().select("name","university_quality","university_type","labels").in("name", unameList));
            if(type > 2 && type < 6){
                unameByTypeList = getUnameList(dtoList, tzExamType, grossScore, resp, type);
            }
        }else {
            universitiesList = o.getUniversitiesList();
            if(type > 2 && type < 6){
                List<UniversitiesMinScoreDTO> dtoList = o.getDtoList();
                unameByTypeList = getUnameList(dtoList, tzExamType, grossScore, resp, type);
            }
        }

        switch (type){
            case 0:
                unameByTypeList = universitiesList.stream().map(Universities::getName).collect(Collectors.toList());
                return getTzRecommendResp(req,wxUser, unameByTypeList, resp, list);
            case 1:
                unameByTypeList = universitiesList.stream().filter(universities -> universities.getUniversityQuality() == 0).map(Universities::getName).collect(Collectors.toList());
                return getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
            case 2:
                unameByTypeList = universitiesList.stream().filter(universities -> universities.getUniversityQuality() == 1).map(Universities::getName).collect(Collectors.toList());
                return getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
            case 3:
                TzRecommendResp sureResp =  getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
                sureResp.getUniversitiesList().forEach(university -> university.setStatus(STATUS_SURE.getCode()));
                return sureResp;
            case 4:
                TzRecommendResp safeResp =  getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
                safeResp.getUniversitiesList().forEach(university -> university.setStatus(STATUS_SAFE.getCode()));
                return safeResp;
            case 5:
                TzRecommendResp sprintResp = getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
                sprintResp.getUniversitiesList().forEach(university -> university.setStatus(STATUS_SPRINT.getCode()));
                return sprintResp;
            case 6:
                unameByTypeList = universitiesList.stream().filter(universities -> universities.getLabels().contains("985")).map(Universities::getName).collect(Collectors.toList());
                return  getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
            case 7:
                unameByTypeList = universitiesList.stream().filter(universities -> universities.getLabels().contains("211")).map(Universities::getName).collect(Collectors.toList());
                return  getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
            case 8:
                unameByTypeList = universitiesList.stream().filter(universities -> universities.getLabels().contains("双一流")).map(Universities::getName).collect(Collectors.toList());
                return  getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
            case 9:
                unameByTypeList = universitiesList.stream().filter(universities -> universities.getLabels().contains("省重点")).map(Universities::getName).collect(Collectors.toList());
                return  getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
            case 10:
                unameByTypeList = universitiesList.stream().filter(universities -> universities.getLabels().contains("港澳")).map(Universities::getName).collect(Collectors.toList());
                return  getTzRecommendResp(req, wxUser, unameByTypeList, resp, list);
            default:
                log.error("暂不支持该类型的查询");
                throw new BizException(UNIVERSITY_LABEL_ERROR.getCode(),UNIVERSITY_LABEL_ERROR.getMessage());
        }
    }


    /**
     * 检查请求
     */
    private static void checkReq(UniversitiesRecommendReq req) throws BizException {
        if(req.getGrossScore() == null || req.getTzExamType() == null || req.getUserId() == null
                || req.getAddress() == null || req.getGender() == null){
            throw new BizException(BODY_NOTNULL.getCode(), BODY_NOTNULL.getMessage());
        }
        // 统招0~750 todo 分数等下一期放到配置表中后管操作
        double grossScore = req.getGrossScore();
        if (grossScore < 0 || grossScore > 750) {
            throw new BizException(TZ_GROSS_SCORE_ERROR.getCode(), TZ_GROSS_SCORE_ERROR.getMessage());
        }

        // 分数低于省控线
        if(req.getTzExamType() == 1 && grossScore < 458){
            throw new BizException(SCORE_UNDER_CONTROLLER_LINE.getCode(), SCORE_UNDER_CONTROLLER_LINE.getMessage());
        }
        if(req.getTzExamType() == 2 && grossScore < 433){
            throw new BizException(SCORE_UNDER_CONTROLLER_LINE.getCode(), SCORE_UNDER_CONTROLLER_LINE.getMessage());
        }

    }


    /**
     * 初始化响应体
     */
    private static TzRecommendResp initResponse(TzRecommendReq req) {
        TzRecommendResp resp = new TzRecommendResp();
        // 1.封装返回参数
        resp.setAddress(req.getAddress());
        resp.setCategory(req.getCategory());
        resp.setGender(req.getGender());
        resp.setScoreSort(req.getScoreSort());
        resp.setGrossScore(req.getGrossScore());
        resp.setUniversitiesList(new ArrayList<>());
        return resp;
    }


    /**
     * 根据分类类型筛选院校
     */
    private TzRecommendResp getTzRecommendResp(TzRecommendReq req,WxUser wxUser,List<String> unameByTypeList, TzRecommendResp resp, List<TzRecommendUniversitiesDTO> list) {
        // 检查list是否为空
        if(unameByTypeList.isEmpty()){
            resp.setTotal(0);
            resp.setUniversitiesList(list);
            return resp;
        }
        // redis中获取院校数据
        TzRecommendDTO o = (TzRecommendDTO) redisUtils.get(TZ_RECOMMEND_LIST + req.getTzExamType() + "/" + req.getGrossScore());
        if(o != null){
            resp.setSafeUniversitiesTotal(o.getSafeUniversitiesTotal());
            resp.setSureUniversitiesTotal(o.getSureUniversitiesTotal());
            resp.setSprintUniversitiesTotal(o.getSprintUniversitiesTotal());
            list = o.getAllUniversities().stream()
                    .filter(universities -> unameByTypeList.contains(universities.getUname()))
                    .collect(Collectors.toList());
        }else {
            TzRecSubTypeQry tzRecSubTypeQry = new TzRecSubTypeQry();
            tzRecSubTypeQry.setTzExamType(req.getTzExamType());
            tzRecSubTypeQry.setUnameByTypeList(unameByTypeList);
            list = universitiesMapper.universitiesList(tzRecSubTypeQry);
        }

        resp.setTotal(list.size());
        // 如果用户未开通vip,则需要限制用户只能看到2个的结果，仅限制稳 保 冲
        if( (req.getType() > 2 && req.getType() < 6) && wxUser.getIsTzVip() != 1){
            limitUniversities(list);
        }
        list.forEach(dto->{
            // 如果是专科，专业置空
            if(dto.getUniversityType() == 1){
                dto.setProfessionList(null);
            }else {
                // 获取专业列表
                List<Profession> professionList = dto.getProfessionList();

                // 遍历每个专业对象
                professionList.forEach(profession -> {
                    // 获取专业批次并设置
                    String batch = batchMap.get(profession.getBatch());
                    String type = (profession.getSubjectType() != null && profession.getSubjectType() == 1) ? WK : LK;
                    profession.setBatchType(batch + type);
                });

                // 设置labels
                String quality = (dto.getUniversityQuality() == 0) ? GOVERNMENT : INDEPENDENT;
                String type = (dto.getUniversityType() == 0) ? BK : ZK;
                String labels = quality + "," + type + (StringUtils.isBlank(dto.getLabels()) ? "" : "," + dto.getLabels());
                dto.setLabels(labels);

                // 筛选科目类别并设置
                List<Profession> professions = professionList.stream()
                        .filter(r -> Objects.equals(r.getSubjectType(), req.getTzExamType()))
//                        .filter(r -> r.getMinSort() != null && req.getScoreSort() < r.getMinSort())
                        .collect(Collectors.toList());
                dto.setProfessionList(professions);
            }
        });
        resp.setUniversitiesList(list);
        return resp;
    }


    /**
     * 检查是否满足智能荐校的条件
     */
    private void checkIsFirst(UniversitiesRecommendReq req) throws BizException {
        if(req.getIsFirst() == 1){
            // 查询用户信息和vip配置信息
            WxUser wxUser = wxUserMapper.selectOne(new QueryWrapper<WxUser>().eq("id", req.getUserId()));
            if(null == wxUser){
                throw new BizException(USER_NOT_REGISTER.getCode(),USER_NOT_REGISTER.getMessage());
            }
            Vip vip = vipMapper.selectOne(new QueryWrapper<Vip>().eq("category", req.getCategory()));
            // 获取当前时间
            LocalDateTime currentDate = LocalDateTime.now();
            // 判断当前时间是否在给定时间段内
            if (vip.getStartTime() != null && vip.getEndTime() != null
                    && !currentDate.isBefore(vip.getStartTime()) && !currentDate.isAfter(vip.getEndTime())) {
                // 收费阶段，判断是否为vip,vip可以额外推荐两次，而普通用户只能推荐一次
                int  maxRecommendNum = wxUser.getIsVip() == 1 ? 3 : 1;
                if (wxUser.getTzRecommendNum() >= maxRecommendNum) {
                    throw new BizException(RECOMMEND_NMU_MAX.getCode(),RECOMMEND_NMU_MAX.getMessage());
                }
                // 如果还有使用次数，先扣除使用次数，再进行评估，失败后回滚
                wxUser.setTzRecommendNum(wxUser.getTzRecommendNum() + 1);
                wxUserMapper.updateById(wxUser);
            }
        }
    }


    /**
     * 保存智能建校结果到redis
     */
    private void saveRecommendList(UniversitiesRecommendReq req, TzRecommendDTO dto, UniversitiesRecommendResp resp, List<Universities> universitiesList, List<TzRecommendUniversitiesDTO> allUniversities, List<UniversitiesMinScoreDTO> dtoList) {
        String tzRecommend = TZ_RECOMMEND + req.getTzExamType() + "/" + req.getGrossScore();
        String tzRecommendList = TZ_RECOMMEND_LIST + req.getTzExamType() + "/" + req.getGrossScore();

        // 封装列表中需要使用的数据
        dto.setSafeUniversitiesTotal(resp.getSafeUniversitiesTotal());
        dto.setSureUniversitiesTotal(resp.getSureUniversitiesTotal());
        dto.setSprintUniversitiesTotal(resp.getSprintUniversitiesTotal());
        dto.setUniversitiesList(universitiesList);
        dto.setAllUniversities(allUniversities);
        dto.setDtoList(dtoList);

        redisUtils.set(tzRecommend,resp,-1);
        redisUtils.set(tzRecommendList, dto,-1);
    }

    /**
     * 设置智能荐校的 保 稳 冲数量，根据type返回对应的院校名称
     */
    private <T extends BaseRecommendResp> List<String> getUnameList(List<UniversitiesMinScoreDTO> dtoList, Integer tzExamType, Double grossScore, T resp,Integer type) {
        List<String> list = new ArrayList<>();
        List<UniversitiesMinScoreDTO> filteredList = dtoList.stream().filter(r -> Objects.equals(r.getSubjectType(), tzExamType)).collect(Collectors.toList());

        long sureNum = filteredList.stream().filter(r -> grossScore > r.getMinScore() + 5).filter(r -> grossScore <= r.getMinScore() + 10).count();
        long safeNum = filteredList.stream().filter(r -> grossScore >= r.getMinScore()).filter(r -> grossScore <= r.getMinScore() + 5).count();
        long sprintNum = filteredList.stream().filter(r -> grossScore >= r.getMinScore() - 5).filter(r -> grossScore < r.getMinScore()).count();
        resp.setSureUniversitiesTotal((int) sureNum);
        resp.setSafeUniversitiesTotal((int) safeNum);
        resp.setSprintUniversitiesTotal((int) sprintNum);

        switch (type) {
            case 3:
                list = filteredList.stream().filter(r -> grossScore > r.getMinScore() + 5).filter(r -> grossScore <= r.getMinScore() + 10).map(UniversitiesMinScoreDTO::getUname).collect(Collectors.toList());
                break;
            case 4:
                list = filteredList.stream().filter(r -> grossScore >= r.getMinScore()).filter(r -> grossScore <= r.getMinScore() + 5).map(UniversitiesMinScoreDTO::getUname).collect(Collectors.toList());
                break;
            case 5:
                list = filteredList.stream().filter(r -> grossScore >= r.getMinScore() - 5).filter(r -> grossScore < r.getMinScore()).map(UniversitiesMinScoreDTO::getUname).collect(Collectors.toList());
                break;
        }
        return list;
    }

    /**
     * 获取院校 - 省控线
     */
    private List<UniversitiesMinScoreDTO> getUniversitiesMinScoreDTOS(Integer tzExamType) {
        String key = TZ_RECOMMEND + "all" + "/" + tzExamType;
        List<UniversitiesMinScoreDTO> dtoList = (List<UniversitiesMinScoreDTO>) redisUtils.get(key); // 从缓存中获取数据
        if (dtoList == null) {
            TzRecYearsQry qry = new TzRecYearsQry();
            qry.setTzExamType(tzExamType);
            qry.setYears(YEARS);
            dtoList = professionMapper.getUnameList(qry); // 从数据库查询数据
            redisUtils.set(key, dtoList, -1); // 将查询结果放入缓存
        }
        return dtoList;
    }

    /**
     * 根据vip限制用户可查看的院校数量
     */
    private void limitUniversities(List<TzRecommendUniversitiesDTO> universities) {
        if (universities.size() > 2) {
            Iterator<TzRecommendUniversitiesDTO> iterator = universities.iterator();
            List<TzRecommendUniversitiesDTO> limitedUniversities = new ArrayList<>();
            limitedUniversities.add(iterator.next());
            limitedUniversities.add(iterator.next());
            // 清空原始集合
            universities.clear();
            // 将限制后的大学重新添加到原始集合中
            universities.addAll(limitedUniversities);
        }
    }

    /**
     * 封装智能荐校的结果
     */
    private static void setRespObject(UniversitiesRecommendReq req, UniversitiesRecommendResp resp, ScoreSort scoreSort) {
        resp.setAddress(req.getAddress());
        resp.setGrossScore(req.getGrossScore());
        resp.setGender(req.getGender());
        resp.setTzExamType(req.getTzExamType());
        resp.setTotal211(0);
        resp.setTotal985(0);
        resp.setGovernmentUniversitiesTotal(0);
        resp.setIndependentUniversitiesTotal(0);
        resp.setDoubleTotal(0);
        resp.setProvinceTotal(0);
        resp.setHkAndMacaoTotal(0);
        resp.setSureUniversitiesTotal(0);
        resp.setSafeUniversitiesTotal(0);
        resp.setSprintUniversitiesTotal(0);
        resp.setScoreSort(scoreSort.getSort());
    }

    /**
     * 封装智能荐校的结果
     */
    private static void setResultObject(UniversitiesRecommendReq req, UniversitiesRecommendResp resp, int total211, int total985, int governmentUniversitiesTotal, int independentUniversitiesTotal, int doubleTotal, int provinceTotal, int hkAndMacaoTotal, ScoreSort scoreSort) {
        resp.setAddress(req.getAddress());
        resp.setGrossScore(req.getGrossScore());
        resp.setGender(req.getGender());
        resp.setTzExamType(req.getTzExamType());
        resp.setTotal211(total211);
        resp.setTotal985(total985);
        resp.setGovernmentUniversitiesTotal(governmentUniversitiesTotal);
        resp.setIndependentUniversitiesTotal(independentUniversitiesTotal);
        resp.setDoubleTotal(doubleTotal);
        resp.setProvinceTotal(provinceTotal);
        resp.setHkAndMacaoTotal(hkAndMacaoTotal);
        resp.setScoreSort(scoreSort.getSort());
    }

}
